﻿//HedgeStrategy.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Strategies
{
	using System;
	using System.Collections.Generic;
	using System.ComponentModel;
	using System.Linq;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.Algo.Derivatives;
	using StockSharp.Logging;
	using StockSharp.BusinessEntities;

	/// <summary>
	/// Базовая стратегия хеджирования.
	/// </summary>
	public abstract class HedgeStrategy : Strategy
	{
		private sealed class AssetStrategy : Strategy
		{
			public AssetStrategy(Security asset)
			{
				if (asset == null)
					throw new ArgumentNullException("asset");

				Name = asset.Id;
			}
		}

		private readonly SynchronizedDictionary<Security, Strategy> _strategies = new SynchronizedDictionary<Security, Strategy>();
		//private bool _isSuspended;
		//private int _reHedgeOrders;
		private Strategy _assetStrategy;
		private readonly HashSet<Order> _awaitingOrders = new HashSet<Order>();
		private readonly object _syncRoot = new object();

		/// <summary>
		/// Инициализировать <see cref="HedgeStrategy"/>.
		/// </summary>
		protected HedgeStrategy()
		{
			BlackScholes = new BasketBlackScholes();

			_useQuoting = new StrategyParam<bool>(this, "UseQuoting");
			_priceOffset = new StrategyParam<Unit>(this, "PriceOffset", new Unit());
		}

		/// <summary>
		/// Портфельная модель расчета значений "греков" по формуле Блэка-Шоулза.
		/// </summary>
		protected BasketBlackScholes BlackScholes { get; private set; }

		private readonly StrategyParam<bool> _useQuoting;

		/// <summary>
		/// Котировать ли выставленную заявку по рыночной цене. По-умолчанию режим выключен.
		/// </summary>
		[Category("Хеджирование")]
		[DisplayName("Котирование")]
		[Description("Котировать ли выставленную заявку по рыночной цене.")]
		public bool UseQuoting
		{
			get { return _useQuoting.Value; }
			set { _useQuoting.Value = value; }
		}

		private readonly StrategyParam<Unit> _priceOffset;

		/// <summary>
		/// Отступ цены для выставляемой заявки. Определяет размер отступа от лучшей котировки (для покупки прибавляется к цене, для продажи - вычитается).
		/// </summary>
		[Category("Хеджирование")]
		[DisplayName("Отступ цены")]
		[Description("Отступ цены для выставляемой заявки.")]
		public Unit PriceOffset
		{
			get { return _priceOffset.Value; }
			set { _priceOffset.Value = value; }
		}

		/// <summary>
		/// Получить список правил, на которые будет реагировать рехеджирование.
		/// </summary>
		/// <returns>Список правил.</returns>
		protected virtual IEnumerable<IMarketRule> GetNotificationRules()
		{
			yield return Security.WhenNewTrades();
		}

		/// <summary>
		/// Метод вызывается тогда, когда вызвался метод <see cref="Strategy.Start"/>, и состояние <see cref="Strategy.ProcessState"/> перешло в значение <see cref="ProcessStates.Started"/>.
		/// </summary>
		protected override void OnStarted()
		{
			base.OnStarted();

			//_reHedgeOrders = 0;
			_awaitingOrders.Clear();

			_strategies.Clear();

			if (_assetStrategy == null)
			{
				_assetStrategy = ChildStrategies.FirstOrDefault(s => s.Security == Security);
				
				if (_assetStrategy == null)
				{
					_assetStrategy = new AssetStrategy(Security);
					ChildStrategies.Add(_assetStrategy);

					this.AddInfoLog("Стратегия для базового актива создана.");
				}
				else
					this.AddInfoLog("Найдена стратегия для базового актива {0}.".Put(_assetStrategy));
			}

			_strategies.Add(Security, _assetStrategy);

			if (BlackScholes.UnderlyingAssetPosition == null)
			{
				BlackScholes.UnderlyingAssetPosition = _assetStrategy.PositionManager;
				this.AddInfoLog("Позиция для базового актива задана.");
			}

			BlackScholes.InnerModels.Clear();

			foreach (var strategy in ChildStrategies)
			{
				if (strategy.Security.Type == SecurityTypes.Option && strategy.Security.GetAsset() == Security)
				{
					BlackScholes.InnerModels.Add(new BasketBlackScholes.InnerModel(new BlackScholes(strategy.Security), strategy.PositionManager));
					_strategies.Add(strategy.Security, strategy);

					this.AddInfoLog("Найдена страйк стратегия {0}.".Put(strategy));
				}
			}

			this.SuspendRules(() =>
				GetNotificationRules().Or()
					.Do(ReHedge)
					.Sync(_syncRoot)
					.Apply(this));

			if (!IsRulesSuspended)
			{
				lock (_syncRoot)
					ReHedge();
			}
		}

		/// <summary>
		/// Получить список заявок, рехеджирующих опционную позицию.
		/// </summary>
		/// <returns>Заявки рехеджирования.</returns>
		protected abstract IEnumerable<Order> GetReHedgeOrders();

		/// <summary>
		/// Добавить стратегию рехеджирования.
		/// </summary>
		/// <param name="parentStrategy">Родительская стратегия (по страйку или по базовому активу).</param>
		/// <param name="order">Заявка рехеджирования.</param>
		protected virtual void AddReHedgeQuoting(Strategy parentStrategy, Order order)
		{
			if (parentStrategy == null)
				throw new ArgumentNullException("parentStrategy");

			var quoting = CreateQuoting(order);

			quoting.Name = parentStrategy.Name + "_" + quoting.Name;

			quoting
				.WhenStopped()
				.Do((rule, s) => TryResumeMonitoring(order))
				.Sync(_syncRoot)
				.Once()
				.Apply(parentStrategy);

			parentStrategy.ChildStrategies.Add(quoting);
		}

		/// <summary>
		/// Добавить заявку рехеджирования.
		/// </summary>
		/// <param name="parentStrategy">Родительская стратегия (по страйку или по базовому активу).</param>
		/// <param name="order">Заявка рехеджирования.</param>
		protected virtual void AddReHedgeOrder(Strategy parentStrategy, Order order)
		{
			var doneRule = order.WhenMatched()
				.Or(order.WhenCanceled())
				.Do((rule, o) =>
				{
					parentStrategy.AddInfoLog("Заявка {0} {1} в {2}.", o.TransactionId, o.IsMatched() ? "исполнена" : "отменена", o.LastChangeTime);

					Rules.RemoveRulesByToken(o, rule);

					TryResumeMonitoring(order);
				})
				.Sync(_syncRoot)
				.Once()
				.Apply(parentStrategy);

			var regRule = order
				.WhenRegistered()
				.Do(o => parentStrategy.AddInfoLog("Заявка {0} зарегистрирована под номером {1} в {2}.", o.TransactionId, o.Id, o.Time))
				.Once()
				.Apply(parentStrategy);

			var regFailRule = order
				.WhenRegisterFailed()
				.Do((rule, fail) =>
				{
					parentStrategy.AddErrorLog("Ошибка регистрации заявки {0}. Описание ошибки {1}.", fail.Order.TransactionId, fail.Error);

					TryResumeMonitoring(order);
					ReHedge();
				})
				.Sync(_syncRoot)
				.Once()
				.Apply(parentStrategy);

			doneRule.Exclusive(regFailRule);
			regRule.Exclusive(regFailRule);

			parentStrategy.RegisterOrder(order);
		}

		/// <summary>
		/// Запустить рехеджирование.
		/// </summary>
		/// <param name="orders">Заявки рехеджирования.</param>
		protected virtual void ReHedge(IEnumerable<Order> orders)
		{
			if (orders == null)
				throw new ArgumentNullException("orders");

			foreach (var order in orders)
			{
				this.AddInfoLog("Рехеджирование заявкой {0} {1} объемом {2} с ценой {3}.", order.Security, order.Direction, order.Volume, order.Price);

				var strategy = _strategies.TryGetValue(order.Security);

				if (strategy == null)
					throw new InvalidOperationException("Для инструмента {0} не создано дочерней стратегии.".Put(order.Security.Id));

				if (UseQuoting)
				{
					AddReHedgeQuoting(strategy, order);
				}
				else
				{
					AddReHedgeOrder(strategy, order);
				}
			}
		}

		/// <summary>
		/// Приостановлено ли рехеджирование.
		/// </summary>
		/// <returns>True, если приостановлено, иначе, false.</returns>
		protected virtual bool IsSuspended()
		{
			return !_awaitingOrders.IsEmpty();
		}

		private void ReHedge()
		{
			if (IsSuspended())
			{
				//this.AddWarningLog("Рехеджирование уже запущено.");
				return;
			}

			//_isSuspended = false;
			_awaitingOrders.Clear();

			var orders = GetReHedgeOrders();

			_awaitingOrders.AddRange(orders);

			if (!_awaitingOrders.IsEmpty())
			{
				this.AddInfoLog("Приостановка правила. Кол-во заявок {0}.", _awaitingOrders.Count);
				ReHedge(orders);
			}
		}

		private void TryResumeMonitoring(Order order)
		{
			if (!_awaitingOrders.Remove(order))
				return;

			if (_awaitingOrders.IsEmpty())
				this.AddInfoLog("Возобновление правила.");
			else
				this.AddInfoLog("Часть заявок реализовано. Оставшееся кол-во заявок {0}.", _awaitingOrders.Count);
		}

		/// <summary>
		/// Создать стратегию котирования для изменения позиции.
		/// </summary>
		/// <param name="order">Заявка, которую необходимо котировать.</param>
		/// <returns>Стратегия котирования.</returns>
		protected virtual QuotingStrategy CreateQuoting(Order order)
		{
			return new MarketQuotingStrategy(order, new Unit(), new Unit()) { Volume = Volume };
		}
	}
}